/* TODO: non-stream arguments */
/* TODO: Wasting a line in delay */
#include "stdsc.h"
#include "xtcstrategies.h"

typedef int reg;
typedef void lmem;

int _ii, _jj, _kk;
int delay[2], adddelay[2][2];

lmem larg0, larg1, larg2, larg3, larg4;
reg  rarg0, rarg1, rarg2, rarg3, rarg4;

/* Compute delays */
delay[0] = delay[1] = 0; /* [instance] */
for (_jj=0; _jj < 2; _jj++)
{
  for (_ii=0; _ii < instance[_jj].arguments; _ii++)
    if (instance[_jj].args[_ii].argtype == ARG_STREAM_OUT)
      delay[_jj] = instance[_jj].args[_ii].state.delay;
}

adddelay[0][0] = adddelay[0][1] = adddelay[1][0] = adddelay[1][1] = 0;
/* [instance][input=0/output=1]. [0][0] = 0. */
if (connections)
{
  adddelay[0][1] = delay[1];
  adddelay[1][0] = delay[0];
}
else
{
  /* No connections, just make sure the outputs have the same delay at the end */
  if (delay[0] < delay[1])
    adddelay[0][1] = delay[1]-delay[0];
  else
    adddelay[1][1] = delay[0]-delay[1];
}

/* Scramble variables of the instances */
instance[0].codes = @scramblevariables(instance[0].codes, "i0");
instance[1].codes = @scramblevariables(instance[1].codes, "i1");

/* Make connections refer to an internal variable. This does not need to be buffered,
 * because all buffers are internal to the operation on XeTaL. */
for (_ii=0; _ii < connections; _ii++)
{
  if (!@equals(instance[0].args[connection[_ii].src].datatype,
	      instance[1].args[connection[_ii].dst].datatype))
    error("Datatypes of arguments ", connection[_ii].src, " and ", connection[_ii].dst,
	  " do not match ");

  @declare(`lmem`, connection[_ii].id);

  for (_jj=0; _jj < 5; _jj++)
  {
    instance[0].codes[_jj] = replace(makeid(`larg`, connection[_ii].src),
				      connection[_ii].id,
				      instance[0].codes[_jj]);
    instance[1].codes[_jj] = replace(makeid(`larg`, connection[_ii].dst),
				      connection[_ii].id,
				      instance[1].codes[_jj]);
  }
}

/* Rename the arguments that are still external */
for (_ii=0; _ii < arguments; _ii++)
{
  if (args[_ii].argtype == ARG_STREAM_IN || args[_ii].argtype == ARG_STREAM_OUT)
  {
    code replacement;

    /* Delay handling */
    if (args[_ii].argtype == ARG_STREAM_IN)
    {
      if (adddelay[args[_ii].connector.instance][0])
      {
	/* Need to add some delay here */
	for (_kk=1; _kk <= adddelay[args[_ii].connector.instance][0]; _kk++)
	  @declarestatic(`lmem`, makeid(makeid(makeid(`_delay`, _ii), `_`), _kk));

	replacement = makeid(makeid(makeid(`_delay`, _ii), `_`), _kk-1);
      }
      else
	replacement = makeid(`__replaced__larg`, _ii);
    }
    else
    {
      if (adddelay[args[_ii].connector.instance][1])
      {
	/* Need to add some delay here */
	for (_kk=1; _kk <= adddelay[args[_ii].connector.instance][1]; _kk++)
	  @declarestatic(`lmem`, makeid(makeid(makeid(`_delay`, _ii), `_`), _kk));

	replacement = makeid(makeid(`_delay`, _ii), `_1`);
      }
      else
	replacement = makeid(`__replaced__larg`, _ii);

      args[_ii].state.delay = args[_ii].state.delay +
	adddelay[args[_ii].connector.instance][0];
    }

    /* Actual replacing */
    for (_jj=0; _jj < 5; _jj++)
    {
      
      instance[args[_ii].connector.instance].codes[_jj] =
	replace(makeid(`larg`, args[_ii].connector.argument),
		replacement,
		instance[args[_ii].connector.instance].codes[_jj]);
    }
  }
  else
  {
    for (_jj=0; _jj < 5; _jj++)
    {
      instance[args[_ii].connector.instance].codes[_jj] =
	replace(makeid(`rarg`, args[_ii].connector.argument),
		makeid(`__replaced__rarg`, _ii),
		instance[args[_ii].connector.instance].codes[_jj]);
    }
  }
}

/* Continuation of the renaming, to avoid nameclashes */
for (_ii=0; _ii < arguments; _ii++)
  for (_jj=0; _jj < 2; _jj++)
    for (_kk=0; _kk < 5; _kk++)
    {
      instance[_jj].codes[_kk] =
	replace(makeid(`__replaced__larg`, _ii),
		makeid(`larg`, _ii),
		instance[_jj].codes[_kk]);
      instance[_jj].codes[_kk] =
	replace(makeid(`__replaced__rarg`, _ii),
		makeid(`rarg`, _ii),
		instance[_jj].codes[_kk]);
    }

@instance[0].codes[PREPROLOGUE];
@instance[1].codes[PREPROLOGUE];

/* Register merged skeleton */
@linebyline(args, arguments)
{ /* Prologue */
  @instance[0].codes[PROLOGUE];
  @instance[1].codes[PROLOGUE];
}
{ /* Body */
  for (_ii=0; _ii < arguments; _ii++)
  {
    if (args[_ii].argtype == ARG_STREAM_OUT)
    {
      if (adddelay[args[_ii].connector.instance][1])
      {
	/* Write and cycle */
	@makeid(`larg`, _ii) = @makeid(makeid(makeid(`_delay`, _ii), `_`),
                                       adddelay[args[_ii].connector.instance][1]);

	for (_kk=adddelay[args[_ii].connector.instance][1]; _kk > 1; _kk--)
	{
	  @makeid(makeid(makeid(`_delay`, _ii), `_`), _kk) =
            @makeid(makeid(makeid(`_delay`, _ii), `_`), _kk-1);
	}
      }
    }
  }
  
  @instance[0].codes[BODY];
  @instance[1].codes[BODY];

  for (_ii=0; _ii < arguments; _ii++)
  {
    if (args[_ii].argtype == ARG_STREAM_IN)
    {
      if (adddelay[args[_ii].connector.instance][0])
      {
	/* Cycle and load */
	for (_kk=adddelay[args[_ii].connector.instance][0]; _kk > 1; _kk--)
	{
	  @makeid(makeid(makeid(`_delay`, _ii), `_`), _kk) =
            @makeid(makeid(makeid(`_delay`, _ii), `_`), _kk-1);
	}

	@makeid(makeid(`_delay`, _ii), `_1`) = @makeid(`larg`, _ii);
      }	
    }
  }
}
{ /* Epilogue */
  @instance[0].codes[EPILOGUE];
  @instance[1].codes[EPILOGUE];
};

@instance[0].codes[POSTEPILOGUE];
@instance[1].codes[POSTEPILOGUE];
